package com.gitee.wsl.ext.reflect

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.reflect.KClass
import kotlin.reflect.KClassifier
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.KTypeProjection

/**
 * A concrete type is a simple type or a parametrized type. See https://kotlinlang.org/spec/type-system.html#type-kinds for its definition.
 */
fun KType.isConcreteTypeWithAllActualKClasses(): Boolean =
    classifier is KClass<*> && arguments.all { it.type?.isConcreteTypeWithAllActualKClasses() ?: false }

val KType.simpleName: String
    get() = when (this.classifier) {
        String::class -> "String"
        Long::class -> "Long"
        Int::class -> "Int"
        Double::class -> "Double"
        Float::class -> "Float"
        Boolean::class -> "Boolean"
        else -> this.classifier.toString()
    }

// TODO: test me
val KType.isPrimitive
    get(): Boolean = classifier in listOf(
        Boolean::class,
        Char::class,
        Byte::class,
        Short::class,
        Int::class,
        Long::class,
        Float::class,
        Double::class
    )

/**
 * Converts a list of unknown typing to a list of a given type.
 * This proves useful since generics are lost when converting ObjC/Swift to Kotlin
 * @return The list of all the elements in the given list that match the desired typing
 */
inline fun <reified T : Any> List<*>.typedList(): List<T> = mapNotNull {
    when (it) {
        is T -> it
        else -> null
    }
}

/**
 * Converts a map of unknown typing to a map of a given key-value type.
 * This proves useful since generics are lost when converting ObjC/Swift to Kotlin
 * @return The map of all the elements in the given map that match the desired typing
 */
inline fun <reified K : Any, reified V : Any> Map<*, *>.typedMap(): Map<K, V> = this.mapNotNull { entry ->
    when (val key = entry.key) {
        is K -> {
            when (val value = entry.value) {
                is V -> Pair(key, value)
                else -> null
            }
        }
        else -> null
    }
}.toMap()

// Sneaky backdoor way of marking a value as non-null to the compiler and skip the null-check
// intrinsic.
// Safe to use (unstable) contracts since they're gone in the final bytecode
@OptIn(ExperimentalContracts::class)
@Suppress("NOTHING_TO_INLINE")
internal inline fun <T> markNotNull(value: T?) {
    contract { returns() implies (value != null) }
}

/** Returns true if [this] and [other] are equal. */
fun KType?.isFunctionallyEqualTo(other: KType?): Boolean {
    if (this === other) {
        return true // Also handles (a == null && b == null).
    }

    markNotNull(this)
    markNotNull(other)

    if (isMarkedNullable != other.isMarkedNullable) return false
    if (!arguments.contentEquals(other.arguments) { a, b -> a.type.isFunctionallyEqualTo(b.type) })
        return false

    // This isn't a supported type.
    when (val classifier = classifier) {
        is KClass<*> -> {
            if (classifier.qualifiedNameForComparison == "kotlin.Array") {
                // We can't programmatically create array types that implement equals fully, as the runtime
                // versions look at the underlying jClass that we can't get here. So we just do a simple
                // check for arrays.
                return (other.classifier as? KClass<*>?)?.qualifiedNameForComparison == "kotlin.Array"
            }
            return classifier == other.classifier
        }
        is KTypeParameter -> {
            val otherClassifier = other.classifier
            if (otherClassifier !is KTypeParameter) return false
            // TODO Use a plain KTypeParameter.equals again once
            // https://youtrack.jetbrains.com/issue/KT-39661 is fixed
            return (classifier.upperBounds.contentEquals(
                otherClassifier.upperBounds,
                KType::isFunctionallyEqualTo,
            ) && (classifier.name == otherClassifier.name))
        }
        else -> return false // This isn't a supported type.
    }
}

private fun <T> List<T>.contentEquals(
    other: List<T>,
    comparator: (a: T, b: T) -> Boolean,
): Boolean {
    if (size != other.size) return false
    for (i in indices) {
        val arg = get(i)
        val otherArg = other[i]
        // TODO do we care about variance?
        if (!comparator(arg, otherArg)) return false
    }
    return true
}

expect fun KType.withNullability(nullable: Boolean): KType

// Not every platform has KType.annotations, so we have to expect/actual this.
// We use a "real" EitherNetKType impl below to actually share all the logic and implement
// via class delegation on platforms.
expect class KTypeImpl(
    classifier: KClassifier?,
    arguments: List<KTypeProjection>,
    isMarkedNullable: Boolean,
    annotations: List<Annotation>,
) : KType, EitherNetKType {
    override val classifier: KClassifier?
    override val arguments: List<KTypeProjection>
    override val isMarkedNullable: Boolean
    override val annotations: List<Annotation>
}




/**
 * Thrown when an error occurs while trying to resolve a [KType] into a concrete [TypeToken]
 */
public class TypeTokenResolutionError(referencedType: KType, context: KType) :
    AssertionError("Error resolving type $referencedType given context of $context")
